;(function($) {

  // НА
  $.extend($, {
    dlgButtons   : function(hOK){return {"OK":hOK,"Отмена":function(){$(this).dialog("close");}};},
    docHandlers  : {},
    addDocHandler: function(e, h)
    {
      if (!$.docHandlers[e])
        $.docHandlers[e] = [];
      $.docHandlers[e].push(h);
      $(document).bind(e, h);
    }
  });
  $.fn.dlgDestroy = function()
  {
    var $doc = $(document);
    this.dialog("destroy").add($doc.find("body>[class^=el-finder]")).remove();
    for (var e in $.docHandlers)
      for (var h in $.docHandlers[e])
        $doc.unbind(e, $.docHandlers[e][h]);
    $.docHandlers = {};
  };

  /**
   * @class  File manager (main controller)
   * @author dio dio@std42.ru
   **/
  elFinder = function(el, o) {
    var self = this, id;
    
    this.log = function(m) {
      window.console && window.console.log && window.console.log(m);
    }
    /**
     * Object. File manager configuration
     **/
    this.options = $.extend({}, this.options, o||{});
    
    if (!this.options.url) {
      alert('Invalid configuration! You have to set URL option.');
      return;
    }
    /**
     * String. element id, create random if not set;
     **/
    this.id = '';
    if ((id = $(el).attr('id'))) {
      this.id = id;
    } else {
      this.id = 'el-finder-'+Math.random().toString().substring(2);
    }
    
    /**
     * String. Version number;
     **/
    this.version  = '1.1 RC3';
    /**
     * String. jQuery version;
     **/
    this.jquery = $.fn.jquery.split('.').join('');

    /**
     * Object. Current Working Dir info
     **/
    this.cwd      = {};
    /**
     * Object. Current Dir Content. Files/folders info
     **/
    this.cdc      = {};
    /**
     * Object. Buffer for copied files
     **/
    this.buffer   = {};
    /**
     * Array. Selected files IDs
     **/
    this.selected = [];
    /**
     * Array. Folder navigation history
     **/
    this.history  = [];
    /**
     * Boolean. Enable/disable actions
     **/
    this.locked   = false;
    /**
     * Number. Max z-index on page + 1, need for contextmenu and quicklook
     **/
    this.zIndex = 2;
    /**
     * DOMElement. jQueryUI dialog
     **/
    this.dialog = null;
    /**
     * DOMElement. For docked mode - place where fm is docked
     **/
    this.anchor = this.options.docked ? $('<div/>').hide().insertBefore(el) : null;
    /**
     * Object. Some options get from server
     **/
    this.params = {dotFiles : false, arc : '', uplMaxSize : '' };
    this.vCookie = 'el-finder-view-'+this.id;
    this.pCookie = 'el-finder-places-'+this.id;
    this.lCookie = 'el-finder-last-'+this.id;
    /**
     * Object. View. After init we can accessel as this.view.win
     **/
    this.view = new this.view(this, el);
    /**
     * Object. User Iterface. Controller for commands/buttons/contextmenu
     **/
    this.ui = new this.ui(this);
    /**
     * Object. Set/update events
     **/
    this.eventsManager = new this.eventsManager(this);
    /**
     * Object. Quick Look like in MacOS X :)
     **/
    this.quickLook = new this.quickLook(this);

    /**
     * Set/get cookie value
     *
     * @param  String  name  cookie name
     * @param  String  value cookie value, null to unset
     **/
    this.cookie = function(name, value) {
      if (typeof value == 'undefined') {
        if (document.cookie && document.cookie != '') {
          var i, c = document.cookie.split(';');
          name += '=';
          for (i=0; i<c.length; i++) {
            c[i] = $.trim(c[i]);
            if (c[i].substring(0, name.length) == name) {
              return decodeURIComponent(c[i].substring(name.length));
            }
          }
        }
        return '';
      } else {
        var d, o = $.extend({}, this.options.cookie);
        if (value===null) {
          value = '';
          o.expires = -1;
        }
        if (typeof(o.expires) == 'number') {
          d = new Date();
          d.setTime(d.getTime()+(o.expires * 24 * 60 * 60 * 1000));
          o.expires = d;
        }
        document.cookie = name+'='+encodeURIComponent(value)+'; expires='+o.expires.toUTCString()+(o.path ? '; path='+o.path : '')+(o.domain ? '; domain='+o.domain : '')+(o.secure ? '; secure' : '');
      }
    }

    /**
     * Set/unset this.locked flag
     *
     * @param  Boolean  state
     **/
    this.lock = function(l) {
      this.view.spinner((this.locked = l||false));
      this.eventsManager.lock = this.locked;
    }

    /**
     * Set/unset lock for keyboard shortcuts
     *
     * @param  Boolean  state
     **/
    this.lockShortcuts = function(l) {
      this.eventsManager.lock = l;
    }
    
    /**
     * Set file manager view type (list|icons)
     *
     * @param  String  v  view name
     **/
    this.setView = function(v) {
      if (v == 'list' || v == 'icons') {
        this.options.view = v;
        this.cookie(this.vCookie, v);
      }
    }
    
    /**
     * make ajax request, show message on error, call callback on success
     *
     * @param  Object.  data for ajax request
     * @param  Function  
     * @param  Object   overrwrite some options 
     */
    this.ajax = function(data, callback, options) {

      var opts = {
        url      : this.options.url,
        async    : true,
        type     : 'GET',
        data     : data,
// НА        dataType : 'json',
        cache    : false,
        lock     : true,
        force    : false,
        silent   : false
      }
      if (typeof(options) == 'object') {
        opts = $.extend({}, opts, options);
      }
      if (!opts.silent) {
        opts.error = self.view.fatal;
      }
      opts.success = function(data) {
        opts.lock && self.lock();
        data.debug && self.log(data.debug);
        if (data.error) {
          !opts.silent && self.view.error(data.error, data.errorData);
          if (!opts.force) {
            return;
          }
        }
        callback(data);
        
        delete data;
      }
      opts.lock && this.lock(true);
      $.ajax(opts);
    }
    
    /**
     * Load generated thumbnails in background
     *
     **/
    this.tmb = function() {
      this.ajax({cmd : 'tmb', current : self.cwd.hash}, function(data) {
        if (self.options.view == 'icons' && data.images && data.current == self.cwd.hash) {
          for (var i in data.images) {
            if (self.cdc[i]) {
              self.cdc[i].tmb = data.images[i];
              $('div[key="'+i+'"]>p', self.view.cwd).css('background', ' url("'+data.images[i]+'") 0 0 no-repeat');
            }
            
          }
          data.tmb && self.tmb();
        }
      }, {lock : false, silent : true});
    }
    
    /**
     * Return folders in places IDs
     *
     * @return Array
     **/
    this.getPlaces = function() {
      var pl = [], p = this.cookie(this.pCookie);
      if (p.length) {
        if (p.indexOf(':')!=-1) {
          pl = p.split(':');
        } else {
          pl.push(p);
        }
      }
      return pl;
    }
    
    /**
     * Add new folder to places
     *
     * @param  String  Folder ID
     * @return Boolean
     **/
    this.addPlace = function(id) {
      var p = this.getPlaces();
      if ($.inArray(id, p) == -1) {
        p.push(id);
        this.savePlaces(p);
        return true;
      }
    }
    
    /**
     * Remove folder from places
     *
     * @param  String  Folder ID
     * @return Boolean
     **/
    this.removePlace = function(id) {
      var p = this.getPlaces();
      if ($.inArray(id, p) != -1) {
        this.savePlaces($.map(p, function(o) {return o == id?null:o; }));
        return true;
      }
    }
    
    /**
     * Save new places data in cookie
     *
     * @param  Array  Folders IDs
     **/
    this.savePlaces = function(p) {
      this.cookie(this.pCookie, p.join(':'));
    }
    
    /**
     * Update file manager content
     *
     * @param  Object  Data from server
     **/
    this.reload = function(data) {
      var i;
      this.cwd = data.cwd;
      this.cdc = {};
      for (i=0; i<data.cdc.length ; i++) {
        this.cdc[data.cdc[i].hash] = data.cdc[i];
        this.cwd.size += data.cdc[i].size;
      }

      if (data.tree) {
        this.view.renderNav(data.tree);
        this.eventsManager.updateNav();
      }

      this.updateCwd();
      
      /* tell connector to generate thumbnails */
      if (data.tmb && !self.locked && self.options.view == 'icons') {
        self.tmb();
      }
      /* have to select some files */
      if (data.select && data.select.length) {
        var l = data.select.length;
        while (l--) {
          this.cdc[data.select[l]] && this.selectById(data.select[l]);
        }
      }
      this.lastDir(this.cwd.hash);
      if (this.options.autoReload>0) {
        this.iID && clearInterval(this.iID);
        this.iID = setInterval(function() { !self.locked && self.ui.exec('reload'); }, this.options.autoReload*60000);
      }
    }
    
    /**
     * Redraw current directory
     *
     */
    this.updateCwd = function() {
      this.lockShortcuts();
      this.selected = [];
      this.view.renderCwd();
      this.eventsManager.updateCwd();
      this.view.tree.find('a[key="'+this.cwd.hash+'"]').trigger('select');
    }
    
    /**
     * Execute after files was dropped onto folder
     *
     * @param  Object  drop event
     * @param  Object  drag helper object
     * @param  String  target folder ID
     */
    this.drop = function(e, ui, target) {
      if (ui.helper.find('[key="'+target+'"]').length) {
        return self.view.error('Unable to copy into itself');
      }
      var ids = [];
      ui.helper.find('div:not(.noaccess):has(>label):not(:has(em[class="readonly"],em[class=""]))').each(function() {
        ids.push($(this).hide().attr('key'));
      });
    
      if (!ui.helper.find('div:has(>label):visible').length) {
        ui.helper.hide();
      }
      if (ids.length) {
        self.setBuffer(ids, e.shiftKey?0:1, target);
        if (self.buffer.files) {
          /* some strange jquery ui bug (in list view) */
          setTimeout(function() {self.ui.exec('paste'); self.buffer = {}}, 300);
        }
      } else {
        $(this).removeClass('el-finder-droppable');
      }
    }
    
    /**
     * Return selected files data
     *
     * @param  Number  if set, returns only element with this index or empty object 
     * @return Array|Object
     */
    this.getSelected = function(ndx) {
      var i, s = [];
      if (ndx>=0) {
        return this.cdc[this.selected[ndx]]||{};
      }
      for (i=0; i<this.selected.length; i++) {
        this.cdc[this.selected[i]] && s.push(this.cdc[this.selected[i]]);
      }
      return s;
    }
    
    this.select = function(el, reset) {
      reset && $('.ui-selected', self.view.cwd).removeClass('ui-selected');
      el.addClass('ui-selected');
      self.updateSelect();
    }

    this.selectById = function(id) {
      var el = $('[key="'+id+'"]', this.view.cwd);
      if (el.length) {
        this.select(el);
        this.checkSelectedPos();
      }
    }

    this.unselect = function(el) {
      el.removeClass('ui-selected');
      self.updateSelect();
    }

    this.toggleSelect = function(el) {
      el.toggleClass('ui-selected');
      this.updateSelect();
    }

    this.selectAll = function() {
      $('[key]', self.view.cwd).addClass('ui-selected')
      self.updateSelect();
    }

    this.unselectAll = function() {
      $('.ui-selected', self.view.cwd).removeClass('ui-selected');
      self.updateSelect();
    }

    this.updateSelect = function() {
      self.selected = [];
      $('.ui-selected', self.view.cwd).each(function() {
        self.selected.push($(this).attr('key'));
      });
      self.view.selectedInfo();
      self.ui.update();
      self.quickLook.update();
    }

    /**
     * Scroll selected element in visible position
     *
     * @param  Boolean  check last or first selected element?
     */
    this.checkSelectedPos = function(last) {
      var s = self.view.cwd.find('.ui-selected:'+(last ? 'last' : 'first')).eq(0),
        p = s.position(),
        h = s.outerHeight(),
        ph = self.view.cwd.height();
      if (p.top < 0) {
        self.view.cwd.scrollTop(p.top+self.view.cwd.scrollTop()-2);
      } else if (ph - p.top < h) {
        self.view.cwd.scrollTop(p.top+h-ph+self.view.cwd.scrollTop());
      }
    }

    /**
     * Add files to clipboard buffer
     *
     * @param  Array   files IDs
     * @param  Boolean copy or cut files?
     * @param  String  destination folder ID
     */
    this.setBuffer = function(files, cut, dst) {
      var i, id, f;
      this.buffer = {
        src   : this.cwd.hash,
        dst   : dst,
        files : [],
        names : [],
        cut   : cut||0
      };
      
      for (i=0; i<files.length; i++) {
        id = files[i]; 
        f = this.cdc[id];
        if (f && f.read && f.type != 'link') {
          this.buffer.files.push(f.hash);
          this.buffer.names.push(f.name);
        }
      }
      
      if (!this.buffer.files.length) {
        this.buffer = {};
      }
    }
    
    /**
     * Return true if file name is acceptable
     *
     * @param  String  file/folder name
     * @return Boolean
     */
    this.isValidName = function(n) {
      if (!this.cwd.dotFiles && n.indexOf('.') == 0) {
        return false;
      }
      return n.match(/^[^\\\/\<\>:]+$/);
    }
    
    /**
     * Return true if file with this name exists
     *
     * @param  String  file/folder name
     * @return Boolean
     */
    this.fileExists = function(n) {
      for (var i in this.cdc) {
        if (this.cdc[i].name == n) {
          return i;
        }
      }
      return false;
    }
    
    /**
     * Return name for new file/folder
     *
     * @param  String  base name (i18n)
     * @param  String  extension for file
     * @return String
     */
    this.uniqueName = function(n, ext) {
      n = self.i18n(n);
      var name = n, i = 0, ext = ext||'';

      if (!this.fileExists(name+ext)) {
        return name+ext;
      }

      while (i++<100) {
        if (!this.fileExists(name+i+ext)) {
          return name+i+ext;
        }
      }
      return name.replace('100', '')+Math.random()+ext;
    }

    /**
     * Get/set last opened dir
     *
     * @param  String  dir hash
     * @return String
     */
    this.lastDir = function(dir) {
      if (this.options.rememberLastDir) {
        return dir ? this.cookie(this.lCookie, dir) : this.cookie(this.lCookie);
      }
    }

    /**
     * Resize file manager
     *
     * @param  Number  width
     * @param  Number  height
     */
    function resize(w, h) {
      w && self.view.win.width(w);
      h && self.view.nav.add(self.view.cwd).height(h);
    }
    
    /**
     * Resize file manager in dialog window while it resize
     *
     */
    function dialogResize() {
      resize(null, self.dialog.height()-self.view.tlb.parent().height()-($.browser.msie ? 47 : 32))
    }

    this.time = function() {
      return new Date().getMilliseconds();
    }

    /* here we init file manager */
    
    this.setView(this.cookie(this.vCookie));
    resize(self.options.width, self.options.height);
    
    /* dialog or docked mode */
    if (this.options.dialog || this.options.docked) {
      this.options.dialog = $.extend({width : 570, dialogClass : '', minWidth : 480, minHeight: 330}, this.options.dialog || {});
      this.options.dialog.dialogClass += 'el-finder-dialog';
      this.options.dialog.resize = dialogResize;
      if (this.options.docked) {
        /* docked mode - create dialog and store size */
        this.options.dialog.close = function() {self.dock(); };
        this.view.win.data('size', {width : this.view.win.width(), height : this.view.nav.height()});
      } else {
        this.dialog = $('<div/>').append(this.view.win).dialog(this.options.dialog);
      }
    }

    this.ajax({
      cmd    : 'open', 
      target : this.lastDir()||'', 
      init   : true, 
      tree   : true 
      }, 
      function(data) {
        if (data.cwd) {
          self.eventsManager.init();
          self.reload(data);
          self.params = data.params;
          // self.log(self.params)
          $('*', document.body).each(function() {
            var z = parseInt($(this).css('z-index'));
            if (z >= self.zIndex) {
              self.zIndex = z+1;
            }
          });
          self.ui.init(data.disabled);
        }
        
    }, {force : true});
      
    
    this.open = function() {
      this.dialog ? this.dialog.dialog('open') : this.view.win.show();
      this.eventsManager.lock = false;
    }
    
    this.close = function() {
      if (this.options.docked && this.view.win.attr('undocked')) {
        this.dock();
      } else {
        this.dialog ? this.dialog.dialog('close') : this.view.win.hide();
      }
      this.eventsManager.lock = true;
    }
    
    this.dock = function() {
      if (this.options.docked && this.view.win.attr('undocked')) {
        var s =this.view.win.data('size');
        this.view.win.insertAfter(this.anchor).removeAttr('undocked');
        resize(s.width, s.height);
      // НА
        this.dialog.dlgDestroy();
        this.dialog = null;
      }
    }
    
    this.undock = function() {
      if (this.options.docked && !this.view.win.attr('undocked')) {
        this.dialog = $('<div/>').append(this.view.win.css('width', '100%').attr('undocked', true).show()).dialog(this.options.dialog);
        dialogResize();
      } 
    }
  }
  
  /**
   * Translate message into selected language
   *
   * @param  String  message in english
   * @param  String  translated or original message
   */
  elFinder.prototype.i18n = function(m) {
    return this.options.i18n[this.options.lang] && this.options.i18n[this.options.lang][m] ? this.options.i18n[this.options.lang][m] :  m;
  }
  
  /**
   * Default config
   *
   */
  elFinder.prototype.options = {
    /* connector url. Required! */
    url            : '',
    /* interface language */
    lang           : 'ru', // НА
    /* additional css class for filemanager container */
    cssClass       : '',
    /* characters number to wrap file name in icons view. set to 0 to disable wrap */
    wrap           : 14,
    /* Name for places/favorites (i18n), set to '' to disable places */
    places         : '', // НА
    /* show places before navigation? */
    placesFirst    : true,
    /* callback to get file url (for wswing editors) */
    editorCallback : null,
    /* string to cut from file url begin before pass it to editorCallback. variants: '' - nothing to cut, 'root' - cut root url, 'http://...' - string if it exists in the beginig of url  */
    cutURL         : '',
    /* close elfinder after editorCallback */
    closeOnEditorCallback : true,
    /* i18 messages. not set manually! */
    i18n           : {},
    /* fm view (icons|list) */
    view           : 'icons',
    /* width to overwrite css options */
    width          : '',
    /* height to overwrite css options. Attenion! this is heigt of navigation/cwd panels! not total fm height */
    height         : '',
    /* disable shortcuts exclude arrows/space */
    disableShortcuts : false,
    /* open last visited dir after reload page or close and open browser */
    rememberLastDir : true,
    /* cookie options */
    cookie         : {
      expires : 30,
      domain  : '',
      path    : '/',
      secure  : false
    },
    /* buttons on toolbar */
    toolbar        : [
      ['back', 'reload'],
      ['select', 'open'],
      ['mkdir', 'upload'], // НА
      ['copy', 'paste'],   // НА
      ['info', 'quicklook'],
      ['icons', 'list'],
      ['help']
    ],
    /* contextmenu commands */
    // НА
    contextmenu : {
      'cwd'   : ['reload', 'delim', 'mkdir', 'upload', 'delim', 'paste', 'delim', 'info'],
      'file'  : ['select', 'open', 'quicklook', 'delim', 'copy', 'duplicate', 'resize', 'archive', 'extract', 'delim', 'info'],
      'group' : ['copy', 'delim', 'archive', 'extract', 'delim', 'info']
    },
    /* jqueryUI dialog options */
    // НА
    dialog : {modal: true, close: function(){$(this).dlgDestroy();}},
    /* docked mode */
    docked : false,
    /* auto reload time (min) */
    autoReload : 0
  }

  
  $.fn.elfinder = function(o) {
    
    return this.each(function() {
      
      var cmd = typeof(o) == 'string' ? o : '';
      if (!this.elfinder) {
        this.elfinder = new elFinder(this, typeof(o) == 'object' ? o : {})
      }
      
      switch(cmd) {
        case 'close':
        case 'hide':
          this.elfinder.close();
          break;
          
        case 'open':
        case 'show':
          this.elfinder.open();
          break;
        
        case 'dock':
          this.elfinder.dock();
          break;
          
        case 'undock':
          this.elfinder.undock();
          break;
      }
      
    })
  }
  
})(jQuery);(function($) {
elFinder.prototype.view = function(fm, el) {
  var self = this;
  this.fm = fm;
  /**
   * Object. Mimetypes to kinds mapping
   **/
  this.kinds = {
    'unknown'                       : 'Unknown',
    'directory'                     : 'Folder',
    'symlink'                       : 'Alias',
    'symlink-broken'                : 'Broken alias',
    'application/x-empty'           : 'Plain text',
    'application/postscript'        : 'Postscript document',
    'application/octet-stream'      : 'Application',
    'application/vnd.ms-office'     : 'Microsoft Office document',
    'application/vnd.ms-word'       : 'Microsoft Word document',  
      'application/vnd.ms-excel'      : 'Microsoft Excel document',
    'application/vnd.ms-powerpoint' : 'Microsoft Powerpoint presentation',
    'application/pdf'               : 'Portable Document Format (PDF)',
    'application/vnd.oasis.opendocument.text' : 'Open Office document',
    'application/x-shockwave-flash' : 'Flash application',
    'application/xml'               : 'XML document', 
    'application/x-bittorrent'      : 'Bittorrent file',
    'application/x-7z-compressed'   : '7z archive',
    'application/x-tar'             : 'TAR archive', 
      'application/x-gzip'            : 'GZIP archive', 
      'application/x-bzip2'           : 'BZIP archive', 
      'application/zip'               : 'ZIP archive',  
      'application/x-rar'             : 'RAR archive',
    'application/javascript'        : 'Javascript application',
    'text/plain'                    : 'Plain text',
      'text/x-php'                    : 'PHP source',
    'text/html'                     : 'HTML document', 
    'text/javascript'               : 'Javascript source',
    'text/css'                      : 'CSS style sheet',  
      'text/rtf'                      : 'Rich Text Format (RTF)',
    'text/rtfd'                     : 'RTF with attachments (RTFD)',
    'text/x-c'                      : 'C source', 
    'text/x-c++'                    : 'C++ source', 
    'text/x-shellscript'            : 'Unix shell script',
      'text/x-python'                 : 'Python source',
    'text/x-java'                   : 'Java source',
    'text/x-ruby'                   : 'Ruby source',
    'text/x-perl'                   : 'Perl script',
      'text/xml'                      : 'XML document', 
    'image/x-ms-bmp'                : 'BMP image',
      'image/jpeg'                    : 'JPEG image',   
      'image/gif'                     : 'GIF Image',    
      'image/png'                     : 'PNG image',
    'image/x-targa'                 : 'TGA image',
      'image/tiff'                    : 'TIFF image',   
      'image/vnd.adobe.photoshop'     : 'Adobe Photoshop image',
    'audio/mpeg'                    : 'MPEG audio',  
    'audio/midi'                    : 'MIDI audio',
    'audio/ogg'                     : 'Ogg Vorbis audio',
    'audio/mp4'                     : 'MP4 audio',
    'audio/wav'                     : 'WAV audio',
    'video/x-dv'                    : 'DV video',
    'video/mp4'                     : 'MP4 video',
    'video/mpeg'                    : 'MPEG video',  
    'video/x-msvideo'               : 'AVI video',
    'video/quicktime'               : 'Quicktime video',
    'video/x-ms-wmv'                : 'WM video',   
    'video/x-flv'                   : 'Flash video',
    'video/x-matroska'              : 'Matroska video'
  }
  
  this.tlb = $('<ul />');

  this.nav = $('<div class="el-finder-nav"/>').resizable({handles : 'e', autoHide : true, minWidth : 200, maxWidth: 500});
  this.cwd = $('<div class="el-finder-cwd"/>').attr('unselectable', 'on');
  this.spn = $('<div class="el-finder-spinner"/>');
  this.err = $('<p class="el-finder-err"><strong/></p>').click(function() {$(this).hide(); });
  this.nfo = $('<div class="el-finder-stat"/>');
  this.pth = $('<div class="el-finder-path"/>');
  this.sel = $('<div class="el-finder-sel"/>');
  this.stb = $('<div class="el-finder-statusbar"/>')
    .append(this.pth)
    .append(this.nfo)
    .append(this.sel);
  this.wrz = $('<div class="el-finder-workzone" />')
    .append(this.nav)
    .append(this.cwd)
    .append(this.spn)
    .append(this.err)
    .append('<div style="clear:both" />');
  this.win = $(el).empty().attr('id', this.fm.id).addClass('el-finder '+(fm.options.cssClass||''))
    .append($('<div class="el-finder-toolbar" />').append(this.tlb))
    .append(this.wrz)
    .append(this.stb);

  this.tree = $('<ul class="el-finder-tree"></ul>').appendTo(this.nav);
  this.plc  = $('<ul class="el-finder-places"><li><a href="#" class="el-finder-places-root"><div/>'+this.fm.i18n(this.fm.options.places)+'</a><ul/></li></ul>').hide();

  this.nav[this.fm.options.placesFirst ? 'prepend' : 'append'](this.plc);

  /*
   * Render ajax spinner
  */
  this.spinner = function(show) {
    this.win.toggleClass('el-finder-disabled', show);
    this.spn.toggle(show);
  }
  
  /*
   * Display ajax error
  */
  this.fatal = function(t) {
    self.error(t.status != '404' ? 'Invalid backend configuration' : 'Unable to connect to backend')
  }
  
  /*
   * Render error
  */
  this.error = function(err, data) {
    this.fm.lock();
    this.err.show().children('strong').html(this.fm.i18n(err)+'!'+this.formatErrorData(data));
    setTimeout(function() {self.err.fadeOut('slow'); }, 4000);
  }
  
  /*
   * Render navigation panel with dirs tree
  */
  this.renderNav = function(tree) {
    var d = tree.dirs.length ? traverse(tree.dirs) : '',
      li = '<li><a href="#" class="el-finder-tree-root" key="'+tree.hash+'"><div'+(d ? ' class="collapsed expanded"' : '')+'/>'+tree.name+'</a>'+d+'</li>';
    this.tree.html(li);
    
    this.fm.options.places && this.renderPlaces();
    
    function traverse(tree) {
      var i, hash, c, html = '<ul style="display:none">';
      for (i=0; i < tree.length; i++) {
        c = '';
        if (!tree[i].read && !tree[i].write) {
          c = 'noaccess';
        } else if (!tree[i].read) {
          c = 'dropbox';
        } else if (!tree[i].write) {
          c = 'readonly';
        } 
        
        html += '<li><a href="#" class="'+c+'" key="'+tree[i].hash+'"><div'+(tree[i].dirs.length ? ' class="collapsed"' : '')+'/>'+tree[i].name+'</a>';

        if (tree[i].dirs.length) {
          html += traverse(tree[i].dirs);
        }
        html += '</li>';
      }
      return html +'</ul>';
    }
  }
  
  /*
   * Render places
  */
  this.renderPlaces = function() {
    var i, c, 
      pl = this.fm.getPlaces(),  
      ul = this.plc.show().find('ul').empty().hide();
    $('div:first', this.plc).removeClass('collapsed expanded');

    if (pl.length) {
      pl.sort(function(a, b) {
        var _a = self.tree.find('a[key="'+a+'"]').text()||'',
          _b = self.tree.find('a[key="'+b+'"]').text()||'';
        return _a.localeCompare(_b);
      });
      
      for (i=0; i < pl.length; i++) {
        if ((c = this.tree.find('a[key="'+pl[i]+'"]:not(.dropbox)').parent()) && c.length) {
          ul.append(c.clone().children('ul').remove().end().find('div').removeClass('collapsed expanded').end());
        } else {
          this.fm.removePlace(pl[i]);
        }
      };
      ul.children().length && $('div:first', this.plc).addClass('collapsed');
    }
  }
  
  /*
   * Render current directory
  */
  this.renderCwd = function() {
    this.cwd.empty();
    
    var num  = 0, size = 0, html = '';
    for (var hash in this.fm.cdc) {
      num++;
      size += this.fm.cdc[hash].size;
      html += this.fm.options.view == 'icons'
        ? this.renderIcon(this.fm.cdc[hash])
        : this.renderRow(this.fm.cdc[hash], num%2);
    }
    if (this.fm.options.view == 'icons') {
      this.cwd.append(html);
    } else {
      this.cwd.append('<table><tr><th colspan="2">'+this.fm.i18n('Name')+'</th><th>'+this.fm.i18n('Permissions')+'</th><th>'+this.fm.i18n('Modified')+'</th><th class="size">'+this.fm.i18n('Size')+'</th><th>'+this.fm.i18n('Kind')+'</th></tr>'+html+'</table>');
    }
    
    this.pth.text(fm.cwd.rel);
    this.nfo.text(fm.i18n('items')+': '+num+', '+this.formatSize(size));
    this.sel.empty();
  }

  /*
   * Render one file as icon
  */
  this.renderIcon = function(f) {
    var str = '<p'+(f.tmb ? ' style="'+"background:url('"+f.tmb+"') 0 0 no-repeat"+'"' : '')+'/><label>'+this.formatName(f.name)+'</label>';
    if (f.link || f.mime == 'symlink-broken') {
      str += '<em/>';
    }
    if (!f.read && !f.write) {
      str += '<em class="noaccess"/>';
    } else if (f.read && !f.write) {
      str += '<em class="readonly"/>';
    } else if (!f.read && f.write) {
      str += '<em class="'+(f.mime == 'directory' ? 'dropbox' :'noread')+'" />';
    }
    return '<div class="'+this.mime2class(f.mime)+'" key="'+f.hash+'">'+str+'</div>';
  }

  /*
   * Render one file as table row
  */
  this.renderRow = function(f, odd) {
    var str = f.link || f.mime =='symlink-broken' ? '<em/>' : '';
    if (!f.read && !f.write) {
      str += '<em class="noaccess"/>';
    } else if (f.read && !f.write) {
      str += '<em class="readonly"/>';
    } else if (!f.read && f.write) {
      str += '<em class="'+(f.mime == 'directory' ? 'dropbox' :'noread')+'" />';
    }
    return '<tr key="'+f.hash+'" class="'+self.mime2class(f.mime)+(odd ? ' el-finder-row-odd' : '')+'"><td class="icon"><p>'+str+'</p></td><td>'+f.name+'</td><td>'+self.formatPermissions(f.read, f.write, f.rm)+'</td><td>'+self.formatDate(f.date)+'</td><td class="size">'+self.formatSize(f.size)+'</td><td>'+self.mime2kind(f.link ? 'symlink' : f.mime)+'</td></tr>';
  }

  /*
   * Re-render file (after editing)
  */
  this.updateFile = function(f) {
    var e = this.cwd.find('[key="'+f.hash+'"]');
    e.replaceWith(e[0].nodeName == 'DIV' ? this.renderIcon(f) : this.renderRow(f));
  }

  /*
   * Update info about selected files
  */
  this.selectedInfo = function() {
    var i, s = 0, sel;
    
    if (self.fm.selected.length) {
      sel = this.fm.getSelected();
      for (i=0; i<sel.length; i++) {
        s += sel[i].size;
      }
    }
    this.sel.text(i>0 ? this.fm.i18n('selected items')+': '+sel.length+', '+this.formatSize(s) : '');
  }

  /*
   * Return wraped file name if needed
  */
  this.formatName = function(n) {
    var w = self.fm.options.wrap;
    if (w>0) {
      if (n.length > w*2) {
        return n.substr(0, w)+"&shy;"+n.substr(w, w-5)+"&hellip;"+n.substr(n.length-3);
      } else if (n.length > w) {
        return n.substr(0, w)+"&shy;"+n.substr(w);
      }
    }
    return n;
  }

  /*
   * Return error message
  */
  this.formatErrorData = function(data) {
    var i, err = ''
    if (typeof(data) == 'object') {
      err = '<br />';
      for (i in data) {
        err += i+' '+self.fm.i18n(data[i])+'<br />';
      }
    }
    return err;
  }

  /*
   * Convert mimetype into css class
  */
  this.mime2class = function(mime) {
    return mime.replace('/', ' ').replace(/\./g, '-');
  }

  /*
   * Return localized date
  */
  this.formatDate = function(d) {
    return d.replace(/([a-z]+)\s/i, function(a1, a2) {return self.fm.i18n(a2)+' '; });
  }

  /*
   * Return formated file size
  */
  this.formatSize = function(s) {
    var n = 1, u = '';
    if (s > 1073741824) {
      n = 1073741824;
      u = 'Gb';
    } else if (s > 1048576) {
            n = 1048576;
            u = 'Mb';
        } else if (s > 1024) {
            n = 1024;
            u = 'Kb';
        }
        return Math.round(s/n)+' '+u;
  }

  /*
   * Return localized string with file permissions
  */
  this.formatPermissions = function(r, w, rm) {
    var p = [];
    r  && p.push(self.fm.i18n('read'));
    w  && p.push(self.fm.i18n('write'));
    rm && p.push(self.fm.i18n('remove'));
    return p.join('/');
  }
  
  /*
   * Return kind of file
  */
  this.mime2kind = function(mime) {
    return this.fm.i18n(this.kinds[mime]||'unknown');
  }
  
}

})(jQuery);/**
 * @class elFinder user Interface. 
 * @author dio dio@std42.ru
 **/
(function($) {
elFinder.prototype.ui = function(fm) {
  
  var self        = this;
  this.fm         = fm;
  this.cmd        = {};
  this.buttons    = {};
  this.menu       = $('<div class="el-finder-contextmenu" />').appendTo(document.body).hide();
  this.dockButton = $('<div class="el-finder-dock-button" title="'+self.fm.i18n('Dock/undock filemanger window')+'" />');
  
  this.exec = function(cmd, arg) {
    if (this.cmd[cmd]) {
      if (cmd != 'open' && !this.cmd[cmd].isAllowed()) {
        return this.fm.view.error('Command not allowed');
      }
      if (!this.fm.locked) {
        this.fm.quickLook.hide();
        $('.el-finder-info').remove();
        this.cmd[cmd].exec(arg);
        this.update();
      }
    }
  }
  
  this.cmdName = function(cmd) {
    if (this.cmd[cmd] && this.cmd[cmd].name) {
      return cmd == 'archive' && this.fm.params.archives.length == 1
        ? this.fm.i18n('Create')+' '+this.fm.view.mime2kind(this.fm.params.archives[0]).toLowerCase()
        : this.fm.i18n(this.cmd[cmd].name);
    }
    return cmd;
  }
  
  this.isCmdAllowed = function(cmd) {
    return self.cmd[cmd] && self.cmd[cmd].isAllowed();
  }
  
  this.execIfAllowed = function(cmd) {
    this.isCmdAllowed(cmd) && this.exec(cmd);
  }
  
  this.includeInCm = function(cmd, t) {
    return this.isCmdAllowed(cmd) && this.cmd[cmd].cm(t);
  }
  
  this.showMenu = function(e) {
    var t, win, size, id = '';
    this.hideMenu();
    
    if (!self.fm.selected.length) {
      t = 'cwd';
    } else if (self.fm.selected.length == 1) {
      t = 'file';
    } else {
      t = 'group';
    }
    
    menu(t);
    
    win = $(window);
    size = {
        height : win.height(),
          width  : win.width(),
          sT     : win.scrollTop(),
          cW     : this.menu.width(),
          cH     : this.menu.height()
      };
    this.menu.css({
        left : ((e.clientX + size.cW) > size.width ? (e.clientX - size.cW) : e.clientX),
        top  : ((e.clientY + size.cH) > size.height && e.clientY > size.cH ? (e.clientY + size.sT - size.cH) : e.clientY + size.sT)
      })
      .show()
      .find('div[name]')
      .hover(
        function() {
          var t = $(this), s = t.children('div'), w;
          t.addClass('hover');
          if (s.length) {
            if (!s.attr('pos')) {
              w  = t.outerWidth();
              s.css($(window).width() - w - t.offset().left > s.width() ? 'left' : 'right', w-5).attr('pos', true);
            }
            s.show();
          }
        }, 
        function() {$(this).removeClass('hover').children('div').hide(); }
     ).click(function(e) {
        e.stopPropagation();
        var t = $(this);
        if (!t.children('div').length) {
          self.hideMenu();
          self.exec(t.attr('name'), t.attr('argc'));
        }
      });
    // self.fm.log(self.menu.html())
    function menu(t) {
      var i, j, a, html, l, src = self.fm.options.contextmenu[t]||[];
      for (i=0; i < src.length; i++) {
        if (src[i] == 'delim') {
          self.menu.children().length && !self.menu.children(':last').hasClass('delim') && self.menu.append('<div class="delim" />');
        } else if (self.fm.ui.includeInCm(src[i], t)) {
          a = self.cmd[src[i]].argc();
          html = '';

          if (a.length) {
            html = '<span/><div class="el-finder-contextmenu-sub" style="z-index:'+(parseInt(self.menu.css('z-index'))+1)+'">';
            for (var j=0; j < a.length; j++) {
              html += '<div name="'+src[i]+'" argc="'+a[j].argc+'" class="'+a[j]['class']+'">'+a[j].text+'</div>';
            };
            html += '</div>';
          }
          self.menu.append('<div class="'+src[i]+'" name="'+src[i]+'">'+html+self.cmdName(src[i])+'</div>');
        }
      };
    }
  }

  this.hideMenu = function() {
    this.menu.hide().empty();
  }

  this.update = function() {
    for (var i in this.buttons) {
      this.buttons[i].toggleClass('disabled', !this.cmd[i].isAllowed());
    }
  }

  this.init = function(disabled) {
    var i, j, n, c=false, zindex = 2, z, t = this.fm.options.toolbar;
    /* disable select command if there is no callback for it */
    if (!this.fm.options.editorCallback) {
      disabled.push('select');
    }
    /* disable archive command if no archivers enabled  */
    if (!self.fm.params.archives.length && $.inArray('archive', disabled) == -1) {
      disabled.push('archive');
    }
    for (i in this.commands) {
      if ($.inArray(i, disabled) == -1) {
        this.commands[i].prototype = this.command.prototype;
        this.cmd[i] = new this.commands[i](this.fm);
      }
    }

    for (i=0; i<t.length; i++) {
      if (c) {
        this.fm.view.tlb.append('<li class="delim" />');
      }
      c = false;
      for (j=0; j<t[i].length; j++) {
        n = t[i][j];
        if (this.cmd[n]) {
          c = true;
          this.buttons[n] = $('<li class="'+n+'" title="'+this.cmdName(n)+'" name="'+n+'" />')
            .appendTo(this.fm.view.tlb)
            .click(function(e) {e.stopPropagation(); })
            .bind('click', (function(ui){return function() {
                !$(this).hasClass('disabled') && ui.exec($(this).attr('name'));
              } })(this)
           ).hover(
              function() {!$(this).hasClass('disabled') && $(this).addClass('el-finder-tb-hover')},
              function() {$(this).removeClass('el-finder-tb-hover')}
           );
        }
      }
    }
    this.update();
    /* set z-index for context menu */
    this.menu.css('z-index', this.fm.zIndex);

    if (this.fm.options.docked) {
      this.dockButton.hover(
        function() {$(this).addClass('el-finder-dock-button-hover')},
        function() {$(this).removeClass('el-finder-dock-button-hover')}
     ).click(function() {
        self.fm.view.win.attr('undocked') ? self.fm.dock() : self.fm.undock();
        $(this).trigger('mouseout');
      }).prependTo(this.fm.view.tlb);
    }

  }

}

/**
 * @class elFinder user Interface Command.
 * @author dio dio@std42.ru
 **/
elFinder.prototype.ui.prototype.command = function(fm) { }

/**
 * Return true if command can be applied now
 * @return Boolean
 **/
elFinder.prototype.ui.prototype.command.prototype.isAllowed = function() {
  return true;
}

/**
 * Return true if command can be included in contextmenu of required type
 * @param  String  contextmenu type (cwd|group|file)
 * @return Boolean
 **/
elFinder.prototype.ui.prototype.command.prototype.cm = function(t) {
  return false;
}

/**
 * Return not empty array if command required submenu in contextmenu
 * @return Array
 **/
elFinder.prototype.ui.prototype.command.prototype.argc = function(t) {
  return [];
}


elFinder.prototype.ui.prototype.commands = {

  /**
   * @class Go into previous folder
   * @param Object  elFinder
   **/
  back : function(fm) {
    var self = this;
    this.name = 'Back';
    this.fm = fm;

    this.exec = function() {
      if (this.fm.history.length) {
        this.fm.ajax({cmd : 'open', target : this.fm.history.pop()  }, function(data) {
          self.fm.reload(data);
        });
      }
    }

    this.isAllowed = function() {
      return this.fm.history.length
    }

  },

  /**
   * @class Reload current directory and navigation panel
   * @param Object  elFinder
   **/
  reload : function(fm) {
    var self  = this;
    this.name = 'Reload';
    this.fm   = fm;

    this.exec = function() {
      this.fm.ajax({cmd : 'open', target : this.fm.cwd.hash, tree : true }, function(data) {
        self.fm.reload(data);
      });
    }

    this.cm = function(t) {
      return t == 'cwd';
    }
  },

  /**
   * @class Open file/folder
   * @param Object  elFinder
   **/
  open : function(fm) {
    var self  = this;
    this.name = 'Open';
    this.fm   = fm;

    /**
     * Open file/folder
     * @param String  file/folder id (only from click on nav tree)
     **/
    this.exec = function(dir) {
      var t = null;
      if (dir) {
        t = {
          hash : $(dir).attr('key'),
          mime : 'directory',
          read : !$(dir).hasClass('noaccess') && !$(dir).hasClass('dropbox')
        }
      } else {
        t = this.fm.getSelected(0);
      }

      if (!t.hash) {
        return;
      }
      if (!t.read) {
        return this.fm.view.error('Access denied');
      }
      if (t.type == 'link' && !t.link) {
        return this.fm.view.error('Unable to open broken link');
      }
      if (t.mime == 'directory') {
        openDir(t.link||t.hash);
      } else {
        openFile(t);
      }

      function openDir(id) {
        self.fm.history.push(self.fm.cwd.hash);
        self.fm.ajax({cmd : 'open', target : id }, function(data) {
          self.fm.reload(data);
        });
      }

      function openFile(f) {
        var s, ws = '';
        if (f.dim) {
          s  = f.dim.split('x');
          ws = 'width='+(parseInt(s[0])+20)+',height='+(parseInt(s[1])+20)+',';
        }
        window.open(f.url||self.fm.options.url+'?cmd=open&current='+(f.parent||self.fm.cwd.hash)+'&target='+(f.link||f.hash), false, 'top=50,left=50,'+ws+'scrollbars=yes,resizable=yes');
      }
    }

    this.isAllowed = function() {
      return this.fm.selected.length == 1 && this.fm.getSelected(0).read;
    }

    this.cm = function(t) {
      return t == 'file';
    }

  },

  /**
   * @class. Return file url
   * @param Object  elFinder
   **/
  select : function(fm) {
    this.name = 'Select file';
    this.fm   = fm;

    this.exec = function() {
      var f = this.fm.getSelected(0);
      if (!f.url) {
        return this.fm.view.error('File URL disabled by connector config');
      }
      this.fm.options.editorCallback(this.fm.options.cutURL == 'root' ? f.url.substr(this.fm.params.url.length) : f.url.replace(new RegExp('^('+this.fm.options.cutURL+')'), ''));
      if (this.fm.options.closeOnEditorCallback) {
        this.fm.dock();
        this.fm.close();
      }
    }

    this.isAllowed = function() {
      return this.fm.selected.length == 1 && !/(symlink\-broken|directory)/.test(this.fm.getSelected(0).mime);
    }

    this.cm = function(t) {
      return t == 'file';
    }
  },

  /**
   * @class. Open/close quickLook window
   * @param Object  elFinder
   **/
  quicklook: function(fm)
  {
    var self = $.extend(this, {
      "name"     : 'Preview with Quick Look',
      "fm"       : fm,
      "exec"     : function(){self.fm.quickLook.toggle();},
      "isAllowed": function(){return this.fm.selected.length==1;},
      "cm"       : function(){return true;}
    });
  },

  /**
   * @class Display files/folders info in dialog window
   * @param Object  elFinder
   **/
  info : function(fm) {
    var self  = this;
    this.name = 'Get info';
    this.fm   = fm;

    /**
     * Open dialog windows for each selected file/folder or for current folder
     **/
    this.exec = function() {
      var f, s, cnt = this.fm.selected.length, w = $(window).width(), h = $(window).height();
      this.fm.lockShortcuts(true);
      if (!cnt) {
        /** nothing selected - show cwd info **/
        info(self.fm.cwd);
      } else {
        /** show info for each selected obj **/
        $.each(this.fm.getSelected(), function() {
          info(this);
        });
      }

      function info(f) {
        var p = ['50%', '50%'], x, y, d,
          tb = '<table cellspacing="0"><tr><td>'+self.fm.i18n('Name')+'</td><td>'+f.name+'</td></tr><tr><td>'+self.fm.i18n('Kind')+'</td><td>'+self.fm.view.mime2kind(f.link ? 'symlink' : f.mime)+'</td></tr><tr><td>'+self.fm.i18n('Size')+'</td><td>'+self.fm.view.formatSize(f.size)+'</td></tr><tr><td>'+self.fm.i18n('Modified')+'</td><td>'+self.fm.view.formatDate(f.date)+'</td></tr><tr><td>'+self.fm.i18n('Permissions')+'</td><td>'+self.fm.view.formatPermissions(f.read, f.write, f.rm)+'</td></tr>';

        if (f.link) {
          tb += '<tr><td>'+self.fm.i18n('Link to')+'</td><td>'+f.linkTo+'</td></tr>';
        }
        if (f.dim) {
          tb += '<tr><td>'+self.fm.i18n('Dimensions')+'</td><td>'+f.dim+' px.</td></tr>';
        }
        if (f.url) {
          tb += '<tr><td>'+self.fm.i18n('URL')+'</td><td><a href="'+f.url+'" target="_blank">'+f.url+'</a></td></tr>';
        }

        if (cnt>1) {
          d = $('.el-finder-dialog-info:last');
          if (!d.length) {
            x = Math.round(((w-350)/2)-(cnt*10));
            y = Math.round(((h-300)/2)-(cnt*10));
            p = [x>20?x:20, y>20?y:20];
          } else {
            x = d.offset().left+10;
            y = d.offset().top+10;
            p = [x<w-350 ? x : 20, y<h-300 ? y : 20];
          }
        }

        $('<div />').append(tb+'</table>').dialog({
          dialogClass : 'el-finder-dialog el-finder-dialog-info',
          width       : 390,
          position    : p,
          title       : self.fm.i18n(f.mime == 'directory' ? 'Folder info' : 'File info'),
        // НА
          close       : function(){if(--cnt<=0){self.fm.lockShortcuts();}$(this).dlgDestroy();},
          buttons     : {OK:function(){$(this).dialog('close');}}
        });
      }
    }

    this.cm = function(t) {
      return true;
    }
  },

  /**
   * @class Rename file/folder
   * @param Object elFinder
   **/
  rename : function(fm) {
    var self  = this;
    this.name = 'Rename';
    this.fm   = fm;

    this.exec = function() {
      var s = this.fm.getSelected(), el, c, input, f, n;

      if (s.length == 1) {
        f  = s[0];
        el = this.fm.view.cwd.find('[key="'+f.hash+'"]');
        c  = this.fm.options.view == 'icons' ? el.children('label') : el.find('td').eq(1);
        n  = c.html();
        input = $('<input type="text" />').val(f.name).appendTo(c.empty())
          .bind('change blur', rename)
          .keydown(function(e) {
            e.stopPropagation();
            if (e.keyCode == 27) {
              restore();
            } else if (e.keyCode == 13) {
              if (f.name == input.val()) {
                restore();
              } else {
                $(this).trigger('change');
              }
            }
          })
          .click(function(e) {e.stopPropagation(); })
          .select()
          .focus();
        this.fm.lockShortcuts(true);
      }

      function restore() {
        c.html(n);
        self.fm.lockShortcuts();
      }

      function rename() {

        if (!self.fm.locked) {
          var err, name = input.val();
          if (f.name == input.val()) {
            return restore();
          }

          if (!self.fm.isValidName(name)) {
            err = 'Invalid name';
          } else if (self.fm.fileExists(name)) {
            err = 'File or folder with the same name already exists';
          }

          if (err) {
            self.fm.view.error(err);
            el.addClass('ui-selected');
            self.fm.lockShortcuts(true);
            return input.select().focus();
          }

          self.fm.ajax({cmd : 'rename', current : self.fm.cwd.hash, target : f.hash, name : name}, function(data) {
            if (data.error) {
              restore();
            } else {
              f.mime == 'directory' && self.fm.removePlace(f.hash) && self.fm.addPlace(data.target);
              self.fm.reload(data);
            }
          }, {force : true });
        }
      }
    }

    /**
     * Return true if only one file selected and has write perms and current dir has write perms
     * @return Boolean
     */
    this.isAllowed = function() {
      return this.fm.cwd.write && this.fm.getSelected(0).write;
    }

    this.cm = function(t) {
      return t == 'file';
    }
  },

  /**
   * @class Copy file/folder to "clipboard"
   * @param Object elFinder
   **/
  copy : function(fm) {
    this.name = 'Copy';
    this.fm   = fm;

    this.exec = function() {
      this.fm.setBuffer(this.fm.selected);
    }

    this.isAllowed = function() {
      if (this.fm.selected.length) {
        var s = this.fm.getSelected(), l = s.length;
        while (l--) {
          if (s[l].read) {
            return true;
          }
        }
      }
      return false;
    }

    this.cm = function(t) {
      return t != 'cwd';
    }
  },

  /**
   * @class Cut file/folder to "clipboard"
   * @param Object elFinder
   **/
  cut : function(fm) {
    this.name = 'Cut';
    this.fm   = fm;

    this.exec = function() {
      this.fm.setBuffer(this.fm.selected, 1);
    }

    this.isAllowed = function() {
      if (this.fm.selected.length) {
        var s = this.fm.getSelected(), l = s.length;
        while (l--) {
          if (s[l].read && s[l].rm) {
            return true;
          }
        }
      }
      return false;
    }

    this.cm = function(t) {
      return t != 'cwd';
    }
  },

  /**
   * @class Paste file/folder from "clipboard"
   * @param Object elFinder
   **/
  paste : function(fm) {
    var self  = this;
    this.name = 'Paste';
    this.fm   = fm;

    this.exec = function() {
      var i, d, f, r, msg = '';

      if (!this.fm.buffer.dst) {
        this.fm.buffer.dst = this.fm.cwd.hash;
      }
      d = this.fm.view.tree.find('[key="'+this.fm.buffer.dst+'"]');
      if (!d.length || d.hasClass('noaccess') || d.hasClass('readonly')) {
        return this.fm.view.error('Access denied');
      }
      if (this.fm.buffer.src == this.fm.buffer.dst) {
        return this.fm.view.error('Unable to copy into itself');
      }
      var o = {
        cmd       : 'paste',
        current   : this.fm.cwd.hash,
        src       : this.fm.buffer.src,
        dst       : this.fm.buffer.dst,
        cut       : this.fm.buffer.cut
      };
      if (this.fm.jquery>132) {
        o.targets = this.fm.buffer.files;
      } else {
        o['targets[]'] = this.fm.buffer.files;
      }
      this.fm.ajax(o, function(data) {
        data.cdc && self.fm.reload(data);
      }, {force : true});
    }

    this.isAllowed = function() {
      return this.fm.buffer.files;
    }

    this.cm = function(t) {
      return t == 'cwd';
    }
  },

  /**
   * @class Remove files/folders
   * @param Object elFinder
   **/
  rm : function(fm) {
    var self  = this;
    this.name = 'Remove';
    this.fm   = fm;

    this.exec = function() {
      var i, ids = [], s =this.fm.getSelected();
      for (var i=0; i < s.length; i++) {
        if (!s[i].rm) {
          return this.fm.view.error(s[i].name+': '+this.fm.i18n('Access denied'));
        }
        ids.push(s[i].hash);
      };
      if (ids.length) {
        this.fm.lockShortcuts(true);
        $('<div><div class="ui-state-error ui-corner-all"><span class="ui-icon ui-icon-alert"/><strong>'+this.fm.i18n('Are you shure you want to remove files?<br /> This cannot be undone!')+'</strong></div></div>')
          .dialog({
            title       : this.fm.i18n('Confirmation required'),
            dialogClass : 'el-finder-dialog',
            width       : 350,
          // НА
            close       : function(){self.fm.lockShortcuts();$(this).dlgDestroy();},
            buttons     : $.dlgButtons(function()
                          {
                            $(this).dialog("close");
                            var o = {cmd: "rm", current: self.fm.cwd.hash};
                            if (132 < self.fm.jquery)
                              o.targets = ids;
                            else
                              o["targets[]"] = ids;
                            self.fm.ajax(o, function(data){if(data.tree)self.fm.reload(data);}, {force : true});
                          })
          });
      }
    }

    this.isAllowed = function(f) {
      if (this.fm.selected.length) {
        var s = this.fm.getSelected(), l = s.length;
        while (l--) {
          if (s[l].rm) {
            return true;
          }
        }
      }
      return false;
    }

    this.cm = function(t) {
      return t != 'cwd';
    }
  },

  /**
   * @class Create new folder
   * @param Object  elFinder
   **/
  mkdir : function(fm) {
    var self  = this;
    this.name = 'New folder';
    this.fm   = fm;

    this.exec = function() {
      self.fm.unselectAll();
      var n     = this.fm.uniqueName('untitled folder');
        input = $('<input type="text"/>').val(n);
        prev  = this.fm.view.cwd.find('.directory:last');
        f     = {name : n, hash : '', mime :'directory', read : true, write : true, date : '', size : 0},
        el    = this.fm.options.view == 'list'
            ? $(this.fm.view.renderRow(f)).children('td').eq(1).empty().append(input).end().end()
            : $(this.fm.view.renderIcon(f)).children('label').empty().append(input).end()
      el.addClass('directory ui-selected');
      if (prev.length) {
        el.insertAfter(prev);
      } else if (this.fm.options.view == 'list') {
        el.insertAfter(this.fm.view.cwd.find('tr').eq(0))
      } else {
        el.prependTo(this.fm.view.cwd)
      }
      self.fm.checkSelectedPos();
      input.select().focus()
        .click(function(e) {e.stopPropagation(); })
        .bind('change blur', mkdir)
        .keydown(function(e) {
          e.stopPropagation();
          if (e.keyCode == 27) {
            el.remove();
            self.fm.lockShortcuts();
          } else if (e.keyCode == 13) {
            mkdir();
          }
        });

      self.fm.lockShortcuts(true);

      function mkdir() {
        if (!self.fm.locked) {
          var err, name = input.val();
          if (!self.fm.isValidName(name)) {
            err = 'Invalid name';
          } else if (self.fm.fileExists(name)) {
            err = 'File or folder with the same name already exists';
          }
          if (err) {
            self.fm.view.error(err);
            self.fm.lockShortcuts(true);
            el.addClass('ui-selected');
            return input.select().focus();
          }

          self.fm.ajax({cmd : 'mkdir', current : self.fm.cwd.hash, name : name}, function(data) {
            if (data.error) {
              el.addClass('ui-selected');
              return input.select().focus();
            }
            self.fm.reload(data);
          }, {force : true});
        }
      }
    }

    this.isAllowed = function() {
      return this.fm.cwd.write;
    }

    this.cm = function(t) {
      return t == 'cwd';
    }
  },

  /**
   * @class Create new text file
   * @param Object  elFinder
   **/
  mkfile : function(fm) {
    var self  = this;
    this.name = 'New text file';
    this.fm   = fm;

    this.exec = function() {
      self.fm.unselectAll();
      var n     = this.fm.uniqueName('untitled file', '.txt'),
        input = $('<input type="text"/>').val(n),
        f     = {name : n, hash : '', mime :'text/plain', read : true, write : true, date : '', size : 0},
        el    = this.fm.options.view == 'list'
          ? $(this.fm.view.renderRow(f)).children('td').eq(1).empty().append(input).end().end()
          : $(this.fm.view.renderIcon(f)).children('label').empty().append(input).end();

      el.addClass('text ui-selected').appendTo(this.fm.options.view == 'list' ? self.fm.view.cwd.children('table') : self.fm.view.cwd);

      input.select().focus()
        .bind('change blur', mkfile)
        .click(function(e) {e.stopPropagation(); })
        .keydown(function(e) {
          e.stopPropagation();
          if (e.keyCode == 27) {
            el.remove();
            self.fm.lockShortcuts();
          } else if (e.keyCode == 13) {
            mkfile();
          }
        });
      self.fm.lockShortcuts(true);

      function mkfile() {
        if (!self.fm.locked) {
          var err, name = input.val();
          if (!self.fm.isValidName(name)) {
            err = 'Invalid name';
          } else if (self.fm.fileExists(name)) {
            err = 'File or folder with the same name already exists';
          }
          if (err) {
            self.fm.view.error(err);
            self.fm.lockShortcuts(true);
            el.addClass('ui-selected');
            return input.select().focus();
          }
          self.fm.ajax({cmd : 'mkfile', current : self.fm.cwd.hash, name : name}, function(data) {
            if (data.error) {
              el.addClass('ui-selected');
              return input.select().focus();
            }
            self.fm.reload(data);
          }, {force : true });
        }
      }

    }

    this.isAllowed = function(f) {
      return this.fm.cwd.write;
    }

    this.cm = function(t) {
      return t == 'cwd';
    }
  },

  /**
   * @class Upload files
   * @param Object  elFinder
   **/
  upload : function(fm) {
    var self  = this;
    this.name = 'Upload files';
    this.fm   = fm;

    this.exec = function() {

      var id = 'el-finder-io-'+(new Date().getTime()),
        e = $('<div class="ui-state-error ui-corner-all"><span class="ui-icon ui-icon-alert"/><div/></div>'),
        m = this.fm.params.uplMaxSize ? '<p>'+this.fm.i18n('Maximum allowed files size')+': '+this.fm.params.uplMaxSize+'</p>' : '',
        b = $('<p class="el-finder-add-field"><span class="ui-state-default ui-corner-all"><em class="ui-icon ui-icon-circle-plus"/></span>'+this.fm.i18n('Add field')+'</p>')
          .click(function() {$(this).before('<p><input type="file" name="upload[]"/></p>'); }),
        f = '<form method="post" enctype="multipart/form-data" action="'+self.fm.options.url+'" target="'+id+'"><input type="hidden" name="cmd" value="upload" /><input type="hidden" name="current" value="'+self.fm.cwd.hash+'" />',
        d = $('<div/>'),
        i = 3;

        while (i--) {f += '<p><input type="file" name="upload[]"/></p>'; }
        f = $(f+'</form>');

        d.append(f.append(e.hide()).prepend(m).append(b)).dialog({
            dialogClass : 'el-finder-dialog',
            title       : self.fm.i18n('Upload files'),
            modal       : true,
            resizable   : false,
        // НА
            close       : function(){self.fm.lockShortcuts();$(this).dlgDestroy();},
            buttons     : $.dlgButtons(function()
                          {
                            if (!f.find(":file[value]").length)
                              return error(self.fm.i18n("Select at least one file to upload"));
                            setTimeout(function()
                            {
                              self.fm.lock();
                              if ($.browser.safari)
                                $.ajax({url: self.fm.options.url, data: {cmd: "ping"}, complete: submit});
                              else
                                submit();
                            });
                            $(this).dialog("close");
                          })
          });

      self.fm.lockShortcuts(true);

      function error(err) {
        e.show().find('div').empty().text(err);
      }

      function submit() {
        var $io = $('<iframe name="'+id+'" name="'+id+'" src="about:blank"/>'),
          io  = $io[0],
          cnt = 50,
          doc, html, data;

        $io.css({position: 'absolute', top: '-1000px', left: '-1000px' })
        .appendTo('body').bind('load', function() {
          $io.unbind('load');
          result();
        });

        self.fm.lock(true);
        f.submit();

        function result() {
          try {
            doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
            /* opera */
            if (doc.body == null || doc.body.innerHTML == '') {
              if (--cnt) {
                return setTimeout(result, 100);
              } else {
                complite();
                return self.fm.view.error('Unable to access iframe DOM after 50 tries');
              }
            }
            /* get server response */
            html = $(doc.body).html();
            if (self.fm.jquery>=141) {
              data = $.parseJSON(html);
            } else if (/^[\],:{}\s]*$/.test(html.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@")
              .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]")
              .replace(/(?:^|:|,)(?:\s*\[)+/g, ""))) {
                /* get from jQuery 1.4 */
              data = window.JSON && window.JSON.parse ? window.JSON.parse(html) : (new Function("return " + html))();
            } else {
              data = {error : 'Unable to parse server response' };
            }
            
          } catch(e) {
            data = {error : 'Unable to parse server response' };
          }
          complite();
          data.error && self.fm.view.error(data.error, data.errorData);
          data.cwd && self.fm.reload(data);
          data.tmb && self.fm.tmb();
        }
        
        function complite() {
          self.fm.lock();
          $io.remove();
        }
          
      }

    }
    
    this.isAllowed = function() {
      return this.fm.cwd.write;
    }
    
    this.cm = function(t) {
      return t == 'cwd';
    }
  },
  
  /**
   * @class Make file/folder copy
   * @param Object  elFinder
   **/
  duplicate : function(fm) {
    var self  = this;
    this.name = 'Duplicate';
    this.fm   = fm;
    
    this.exec = function() {
      this.fm.ajax({
        cmd     : 'duplicate',
        current : this.fm.cwd.hash,
        target  : this.fm.selected[0]
      },
      function(data) {
        self.fm.reload(data);
      });
    }
    
    this.isAllowed = function() {
      return this.fm.cwd.write && this.fm.selected.length == 1 && this.fm.getSelected()[0].read;
    }
    
    this.cm = function(t) {
      return t == 'file';
    }
  },
  
  /**
   * @class Edit text file
   * @param Object  elFinder
   **/
  edit : function(fm) {
    var self  = this;
    this.name = 'Edit text file';
    this.fm   = fm;
    
    this.exec = function() {
      var f = this.fm.getSelected(0);
      this.fm.lockShortcuts(true);
      this.fm.ajax({
        cmd     : 'read',
        current : this.fm.cwd.hash,
        target    : f.hash
      }, function(data) {
        self.fm.lockShortcuts(true);
        var ta= $('<textarea/>').val(data.content||'').keydown(function(e) {e.stopPropagation(); });
        $('<div/>').append(ta)
          .dialog({
            dialogClass : 'el-finder-dialog',
            title   : self.fm.i18n(self.name),
            modal   : true,
            width   : 500,
          // НА
            close   : function(){self.fm.lockShortcuts();$(this).dlgDestroy();},
            buttons : $.dlgButtons(function()
                      {
                        var c = ta.val();
                        $(this).dialog("close");
                        self.fm.ajax({cmd: "edit", current: self.fm.cwd.hash, target: f.hash, content: c}, function(data)
                        {
                          if (data.target)
                          {
                            self.fm.cdc[data.target.hash] = data.target;
                            self.fm.view.updateFile(data.target);
                            self.fm.selectById(data.target.hash);
                          }
                        }, {type: "post"});
                      })
          });
      });
    }
    
    this.isAllowed = function() {
      if (self.fm.selected.length == 1) {
        var f = this.fm.getSelected()[0];
        return f.write && (f.mime.indexOf('text') == 0 || f.mime == 'application/x-empty' || f.mime == 'application/xml');
      }
    }
    
    this.cm = function(t) {
      return t == 'file';
    }
  },
  
  /**
   * @class Create archive
   * @param Object  elFinder
   **/
  archive : function(fm) {
    var self  = this;
    this.name = 'Create archive';
    this.fm   = fm;
    
    this.exec = function(t) {
      var o = {
        cmd     : 'archive',
        current : self.fm.cwd.hash,
        type    : $.inArray(t, this.fm.params.archives) != -1 ? t : this.fm.params.archives[0],
        name    : self.fm.i18n('Archive')
      };
      if (this.fm.jquery>132) {
        o.targets = self.fm.selected;
      } else {
        o['targets[]'] = self.fm.selected;
      }
      
      this.fm.ajax(o, function(data) {self.fm.reload(data); });
    }
    
    this.isAllowed = function() {
      if (this.fm.cwd.write && this.fm.selected.length) {
        var s = this.fm.getSelected(), l = s.length;
        while (l--) {
          if (s[l].read) {
            return true;
          }
        }
      }
      return false;
    }
    
    this.cm = function(t) {
      return t != 'cwd';
    }
    
    this.argc = function() {
      var i, v = [];
      for (i=0; i < self.fm.params.archives.length; i++) {
        v.push({
          'class' : 'archive',
          'argc'  : self.fm.params.archives[i],
          'text'  : self.fm.view.mime2kind(self.fm.params.archives[i])
        });
      };
      return v;
    }
  },
  
  /**
   * @class Extract files from archive
   * @param Object  elFinder
   **/
  extract : function(fm) {
    var self  = this;
    this.name = 'Uncompress archive';
    this.fm   = fm;
    
    this.exec = function() {
      this.fm.ajax({
        cmd     : 'extract',
        current : this.fm.cwd.hash,
        target  : this.fm.getSelected(0).hash
      }, function(data) {
        self.fm.reload(data);
      })
    }
    
    this.isAllowed = function() {
      return this.fm.cwd.write && this.fm.selected.length == 1 && this.fm.getSelected(0).read && this.fm.params.extract.length && $.inArray(this.fm.getSelected(0).mime, this.fm.params.extract) != -1;
    }
    
    this.cm = function(t) {
      return t == 'file';
    }
  },
  
  /**
   * @class Resize image
   * @param Object  elFinder
   **/
  resize : function(fm) {
    var self  = this;
    this.name = 'Resize image';
    this.fm   = fm;
    
    this.exec = function() {
      var s = this.fm.getSelected();
      if (s[0] && s[0].write && s[0].dim) {
        var size = s[0].dim.split('x'), 
          w  = parseInt(size[0]), 
          h  = parseInt(size[1]), rel = w/h
          iw = $('<input type="text" size="9" value="'+w+'" name="width"/>'),
          ih = $('<input type="text" size="9" value="'+h+'" name="height"/>'),
          f  = $('<form/>').append(iw).append(' x ').append(ih).append(' px');
        iw.add(ih).bind('change', calc);
        self.fm.lockShortcuts(true);
        var d = $('<div/>').append($('<div/>').text(self.fm.i18n('Dimensions')+':')).append(f).dialog({
          title : self.fm.i18n('Resize image'),
          dialogClass : 'el-finder-dialog',
          width : 230,
          modal : true,
        // НА
          close  : function(){self.fm.lockShortcuts();$(this).dlgDestroy();},
          buttons: $.dlgButtons(function()
                   {
                     var _w = parseInt(iw.val());
                     var _h = parseInt(ih.val());
                     if (_w > 0 && _w != w && _h > 0 && _h != h)
                       self.fm.ajax({
                         cmd    : "resize",
                         current: self.fm.cwd.hash,
                         target : s[0].hash,
                         width  : _w,
                         height : _h
                       }, function(data){self.fm.reload(data);});
                     $(this).dialog("close");
                   })
        });
      } 

      function calc() {
        var _w = parseInt(iw.val()) || 0,
          _h = parseInt(ih.val()) || 0;
          
        if (_w<=0 || _h<=0) {
          _w = w;
          _h = h;
        } else if (this == iw.get(0)) {
          _h = parseInt(_w/rel);
        } else {
          _w = parseInt(_h*rel);
        }
        iw.val(_w);
        ih.val(_h);
      }
      
    }
    
    this.isAllowed = function() {
      return this.fm.selected.length == 1 && this.fm.cdc[this.fm.selected[0]].write && this.fm.cdc[this.fm.selected[0]].resize;
    }
    
    this.cm = function(t) {
      return t == 'file';
    }
  },
  
  /**
   * @class Switch elFinder into icon view
   * @param Object  elFinder
   **/
  icons : function(fm) {
    this.name = 'View as icons';
    this.fm   = fm;
    
    this.exec = function() {
      this.fm.view.win.addClass('el-finder-disabled');
      this.fm.setView('icons');
      this.fm.updateCwd();
      this.fm.view.win.removeClass('el-finder-disabled');
      $('div.image', this.fm.view.cwd).length && this.fm.tmb();
    }
    
    this.isAllowed = function() {
      return this.fm.options.view != 'icons';
    }
    
    this.cm = function(t) {
      return t == 'cwd';
    }
  },
  
  /**
   * @class Switch elFinder into list view
   * @param Object  elFinder
   **/
  list : function(fm) {
    this.name = 'View as list';
    this.fm   = fm;
    
    this.exec = function() {
      this.fm.view.win.addClass('el-finder-disabled');
      this.fm.setView('list');
      this.fm.updateCwd();
      this.fm.view.win.removeClass('el-finder-disabled');
    }
    
    this.isAllowed = function() {
      return this.fm.options.view != 'list';
    }
    
    this.cm = function(t) {
      return t == 'cwd';
    }
  },
  
  help : function(fm) {
    this.name = 'Help';
    this.fm   = fm;
    
    this.exec = function() {
      var h, ht = this.fm.i18n('helpText'), a, s, tabs; 
      
      h = '<div class="el-finder-logo"/><strong>'+this.fm.i18n('elFinder: Web file manager')+'</strong><br/>'+this.fm.i18n('Version')+': '+this.fm.version+'<br clear="all"/>'
        +'<p><strong><a href="http://elrte.ru/donate?prod=elfinder&lang='+this.fm.options.lang+'" target="_blank">'+this.fm.i18n('Donate to support project development')+'</a></strong></p>'
        + '<p><a href="http://elrte.ru/redmine/wiki/elfinder/" target="_blank">'+this.fm.i18n('elFinder documentation')+'</a></p>';
      h += '<p>'+(ht != 'helpText' ? ht : 'elFinder works similar to file manager on your computer. <br /> To make actions on files/folders use icons on top panel. If icon action it is not clear for you, hold mouse cursor over it to see the hint. <br /> Manipulations with existing files/folders can be done through the context menu (mouse right-click).<br/> To copy/delete a group of files/folders, select them using Shift/Alt(Command) + mouse left-click.')+'</p>';
      h += '<p>'
        + '<strong>'+this.fm.i18n('elFinder support following shortcuts')+':</strong><ul>'
        + '<li><kbd>Ctrl+A</kbd> - '+this.fm.i18n('Select all files')+'</li>'
         + '<li><kbd>Ctrl+C/Ctrl+X/Ctrl+V</kbd> - '+this.fm.i18n('Copy/Cut/Paste files')+'</li>'
         + '<li><kbd>Enter</kbd> - '+this.fm.i18n('Open selected file/folder')+'</li>'
        + '<li><kbd>Space</kbd> - '+this.fm.i18n('Open/close QuickLook window')+'</li>'
         + '<li><kbd>Delete/Cmd+Backspace</kbd> - '+this.fm.i18n('Remove selected files')+'</li>'
         + '<li><kbd>Ctrl+I</kbd> - '+this.fm.i18n('Selected files or current directory info')+'</li>'
         + '<li><kbd>Ctrl+N</kbd> - '+this.fm.i18n('Create new directory')+'</li>'
         + '<li><kbd>Ctrl+U</kbd> - '+this.fm.i18n('Open upload files form')+'</li>'
         + '<li><kbd>Left arrow</kbd> - '+this.fm.i18n('Select previous file')+'</li>'
         + '<li><kbd>Right arrow </kbd> - '+this.fm.i18n('Select next file')+'</li>'
         + '<li><kbd>Ctrl+Right arrow</kbd> - '+this.fm.i18n('Open selected file/folder')+'</li>'
         + '<li><kbd>Ctrl+Left arrow</kbd> - '+this.fm.i18n('Return into previous folder')+'</li>'
         + '<li><kbd>Shift+arrows</kbd> - '+this.fm.i18n('Increase/decrease files selection')+'</li></ul>'
         + '</p><p>'
         + this.fm.i18n('Contacts us if you need help integrating elFinder in you products')+': dev@std42.ru</p>';

      a = '<div class="el-finder-help-std"/>'
        +'<p>'+this.fm.i18n('Javascripts/PHP programming: Dmitry (dio) Levashov, dio@std42.ru')+'</p>'
        +'<p>'+this.fm.i18n('Python programming, techsupport: Troex Nevelin, troex@fury.scancode.ru')+'</p>'
        +'<p>'+this.fm.i18n('Design: Valentin Razumnih')+'</p>'
        +'<p>'+this.fm.i18n('Spanish localization')+': Alex (xand) Vavilin, xand@xand.es, <a href="http://xand.es" target="_blank">http://xand.es</a></p>'
        +'<p>'+this.fm.i18n('Icons')+': <a href="http://www.famfamfam.com/lab/icons/silk/" target="_blank">Famfam silk icons</a>, <a href="http://www.fatcow.com/free-icons/" target="_blank">Fatcow icons</a>'+'</p>'
        +'<p>'+this.fm.i18n('Copyright: <a href="http://www.std42.ru" target="_blank">Studio 42 LTD</a>')+'</p>'
        +'<p>'+this.fm.i18n('License: BSD License')+'</p>'
        +'<p>'+this.fm.i18n('Web site: <a href="http://www.elrte.ru/elfinder/" target="_blank">elrte.ru</a>')+'</p>';
      
      s = '<div class="el-finder-logo"/><strong><a href="http://www.eldorado-cms.ru" target="_blank">ELDORADO.CMS</a></strong><br/>'
        +this.fm.i18n('Simple and usefull Content Management System')
        +'<hr/>'
        + this.fm.i18n('Support project development and we will place here info about you');
      
      tabs = '<ul><li><a href="#el-finder-help-h">'+this.fm.i18n('Help')+'</a></li><li><a href="#el-finder-help-a">'+this.fm.i18n('Authors')+'</a><li><a href="#el-finder-help-sp">'+this.fm.i18n('Sponsors')+'</a></li></ul>'
          +'<div id="el-finder-help-h"><p>'+h+'</p></div>'
          +'<div id="el-finder-help-a"><p>'+a+'</p></div>'
          +'<div id="el-finder-help-sp"><p>'+s+'</p></div>';
      
      var d = $('<div/>').html(tabs).dialog({
        width       : 617,
        title       : this.fm.i18n('Help'),
        dialogClass : 'el-finder-dialog',
        modal       : true,
      // НА
        close      : function(){d.tabs('destroy').dlgDestroy();},
        buttons    : {OK: function(){$(this).dialog('close');}}
      }).tabs();
    }

    this.cm = function(t) {
      return t == 'cwd';
    }
  }
}

})(jQuery);

/**
 * @class Create quick look window (similar to MacOS X Quick Look)
 * @author dio dio@std42.ru
 **/
(function($) {
elFinder.prototype.quickLook = function(fm, el) {
  var self    = this;
  this.fm     = fm;
  this._hash  = '';
  this.title  = $('<strong/>');
  this.ico    = $('<p/>');
  this.info   = $('<label/>');
  this.media  = $('<div class="el-finder-ql-media"/>').hide()
  
  this.name = $('<span class="el-finder-ql-name"/>')
  this.kind = $('<span class="el-finder-ql-kind"/>')
  this.size = $('<span class="el-finder-ql-size"/>')
  this.date = $('<span class="el-finder-ql-date"/>')
  this.url  = $('<a href="#"/>').hide().click(function(e) {
    e.preventDefault();
    window.open($(this).attr('href'));
    self.hide();
  });

  this.add = $('<div/>')
  this.content = $('<div class="el-finder-ql-content"/>')
  this.win     = $('<div class="el-finder-ql"/>').hide()
    .append($('<div class="el-finder-ql-drag-handle"/>').append($('<span class="ui-icon ui-icon-circle-close"/>').click(function() {self.hide(); })).append(this.title))
    .append(this.ico)
    .append(this.media)
    .append(this.content.append(this.name).append(this.kind).append(this.size).append(this.date).append(this.url).append(this.add))
    .appendTo(document.body)
    .draggable({handle : '.el-finder-ql-drag-handle'})
    .resizable({
      minWidth  : 420,
      minHeight : 150,
      resize    : function() {
        if (self.media.children().length) {
          var t = self.media.children(':first');
          switch (t[0].nodeName) {
            case 'IMG':
              var w = t.width(),
                h = t.height(),
                _w = self.win.width(),
                _h = self.win.css('height') == 'auto' ? 350 : self.win.height()-self.content.height()-self.th,
                r = w>_w || h>_h 
                  ? Math.min(Math.min(_w, w)/w, Math.min(_h, h)/h) 
                  : Math.min(Math.max(_w, w)/w, Math.max(_h, h)/h);
              t.css({
                width : Math.round(t.width()*r),
                height : Math.round(t.height()*r)
              })
              break;
            case 'IFRAME':
            case 'EMBED':
              t.css('height', self.win.height()-self.content.height()-self.th);
              break;
            case 'OBJECT':
              t.children('embed').css('height', self.win.height()-self.content.height()-self.th);
          }
        }
      }
    });
  
  this.th = parseInt(this.win.children(':first').css('height'))||18;
  /* All browsers do it, but some is shy to says about it. baka da ne! */
  this.mimes = {
    'image/jpeg' : 'jpg',
    'image/gif'  : 'gif',
    'image/png'  : 'png'
    };

  for (var i=0; i<navigator.mimeTypes.length; i++) {
    var t = navigator.mimeTypes[i].type;
    if (t && t != '*') {
      this.mimes[t] = navigator.mimeTypes[i].suffixes;
    }
  }
  
  if (($.browser.safari && navigator.platform.indexOf('Mac') != -1) || $.browser.msie) {
    /* not booletproof, but better then nothing */
    this.mimes['application/pdf'] = 'pdf';
  } else {
    for (var n=0; n < navigator.plugins.length; n++) {
      for (var m=0; m < navigator.plugins[n].length; m++) {
        var e = navigator.plugins[n][m].description.toLowerCase();
        if (e.substring(0, e.indexOf(" ")) == 'pdf') {
          this.mimes['application/pdf'] = 'pdf';
          break;
        }
      }
    }
  }
  
  if (this.mimes['image/x-bmp']) {
    this.mimes['image/x-ms-bmp'] = 'bmp';
  }
  
  if ($.browser.msie && !this.mimes['application/x-shockwave-flash']) {
    this.mimes['application/x-shockwave-flash'] = 'swf';
  }

  /**
   * Open quickLook window
   **/
  this.show = function()
  {
    if (this.win.is(':hidden') && (self.fm.selected.length == 1))
    {
      update();
      var id = self.fm.selected[0],
          el = self.fm.view.cwd.find(['[key="', '"]'].join(id)),
          o  = $.extend({
            display: "block", // НА
            width  : el.width() - 20,
            height : el.height(),
            opacity: 0
          }, el.offset());
      self.fm.lockShortcuts(true);
      this.win.css(o).animate({
        width  : 420,
        height : 150,
        opacity: 1,
        top    : Math.round($(window).height() / 5),
        left   : $(window).width() / 2 - 210
      }, 450, function(){self.win.css('height','auto');self.fm.lockShortcuts();});
    }
  }

  /**
   * Close quickLook window
   **/
  this.hide = function()
  {
    if (this.win.is(':visible'))
    {
      var o, el = self.fm.view.cwd.find(['[key="', '"]'].join(this._hash));
      if (el)
      {
        o = el.offset();
        this.media.hide(200);
        this.win.animate({
          width  : el.width() - 20,
          height : el.height(),
          left   : o.left,
          top    : o.top,
          opacity: 0
        }, 350, function(){self.fm.lockShortcuts();reset();self.win.hide().css('height','auto');});
      }
      else
      {
        this.win.fadeOut(200);
        reset();
        self.fm.lockShortcuts();
      }
    }
  }

  /**
   * Open/close quickLook window
   **/
  this.toggle = function() {
    if (this.win.is(':visible')) {
      this.hide();
    } else {
      this.show();
    }
  }
  
  /**
   * Update quickLook window content if only one file selected,
   * otherwise close window
   **/
  this.update = function() {
    if (this.fm.selected.length != 1) {
      this.hide();
    } else if (this.win.is(':visible') && this.fm.selected[0] != this._hash) {
      update();
    }
  }
  
  /**
   * Return height of this.media block
   * @return Number
   **/
  this.mediaHeight = function() {
    return this.win.is(':animated') || this.win.css('height') == 'auto' ? 315 : this.win.height()-this.content.height()-this.th;
  }
  
  /**
   * Clean quickLook window DOM elements
   **/
  function reset() {
    self.media.hide().empty();
    self.win.attr('class', 'el-finder-ql').css('z-index', self.fm.zIndex);
    self.title.empty();
    self.ico.removeAttr('style').show();
    self.add.hide().empty();
    self._hash = '';
  }
  
  /**
   * Update quickLook window content
   **/
  function update() {
    var f = self.fm.getSelected(0);
    reset();

    self._hash = f.hash;
    self.title.text(f.name);
    self.win.addClass(self.fm.view.mime2class(f.mime));
    self.name.text(f.name);
    self.kind.text(self.fm.view.mime2kind(f.link ? 'symlink' : f.mime)); 
    self.size.text(self.fm.view.formatSize(f.size));
    self.date.text(self.fm.i18n('Modified')+': '+self.fm.view.formatDate(f.date));
    f.dim && self.add.append('<span>'+f.dim+' px</span>').show();
    f.tmb && self.ico.css('background', 'url("'+f.tmb+'") 0 0 no-repeat');
    if (f.url) {
      self.url.text(f.url).attr('href', f.url).show();
      for (var i in self.plugins) {
        if (self.plugins[i].test && self.plugins[i].test(f.mime, self.mimes, f.name)) {
          self.plugins[i].show(self, f);
          return;
        }
      }
    } else {
      self.url.hide();
    }
    
    self.win.css({
      width  : '420px',
      height : 'auto'
    });
  }

}

elFinder.prototype.quickLook.prototype.plugins =
{
  image: new function()
  {
    this.test = function(mime,mimes){return mime.match(/^image\//);};
    this.show = function(ql, f)
    {
      var preview = function(img)
      {
        var w  = img.width(),
            h  = img.height(),
            a  = ql.win.is(':animated'),
            _w = a ? 420 : ql.win.width(),
            _h = a || (ql.win.css('height') == 'auto') ? 315 : ql.win.height() - ql.content.height() - ql.th,
            r  = w > _w || h > _h ? Math.min(Math.min(_w, w) / w, Math.min(_h, h) / h) : Math.min(Math.max(_w, w) / w, Math.max(_h, h) / h);
        ql.fm.lockShortcuts(true);
        ql.ico.hide();
        img.
          show(). // НА
          css({width: ql.ico.width(), height: ql.ico.height()}).
          animate({width: Math.round(r * w), height: Math.round(r * h)}, 450, function(){ql.fm.lockShortcuts();});
      };
      if (ql.mimes[f.mime] && (f.hash == ql._hash))
        $('<img />').
          hide().
          appendTo(ql.media.show()).
          attr('src', $.browser.msie || $.browser.opera ? [f.url, Math.random()].join("?") : f.url).
          load(function()
          {
            var t = $(this).unbind('load');
            if (f.hash == ql._hash)
              if (ql.win.is(':animated'))
                setTimeout(function(){preview(t);}, 330);
              else
                preview(t);
          });
    };
  },

  text: new function()
  {
    this.test = function(mime, mimes)
    {
      return ((mime.indexOf('text') == 0) && (mime.indexOf('rtf') == -1)) || mime.match(/application\/(xml|javascript|json)/);
    };
    this.show = function(ql, f)
    {
      if (f.hash == ql._hash)
      {
        ql.ico.hide();
        ql.media.append(['<iframe src="', f.url, '" style="height:', ql.mediaHeight(), 'px" />'].join("")).show();
      }
    };
  },

  swf: new function()
  {
    this.test = function(mime,mimes){return(mime=='application/x-shockwave-flash')&&mimes[mime];};
    this.show = function(ql, f)
    {
      if (f.hash == ql._hash)
      {
        ql.ico.hide();
        var e = ql.media.append(['<embed pluginspage="http://www.macromedia.com/go/getflashplayer" quality="high" src="', f.url, '" style="width:100%;height:', ql.mediaHeight(), 'px" type="application/x-shockwave-flash" />'].join(""));
        if (ql.win.is(':animated'))
          e.slideDown(450)
        else
          e.show()
      }
    };
  },

  audio: new function()
  {
    this.test = function(mime,mimes){return!mime.indexOf('audio')&&mimes[mime];};
    this.show = function(ql, f)
    {
      if (f.hash == ql._hash)
      {
        ql.ico.hide();
        ql.media.append([
          '<embed src="',
          f.url,
          '" style="width:100%;height:',
          ql.win.is(':animated') || (ql.win.css('height') == 'auto') ? 100 : ql.win.height() - ql.content.height() - ql.th,
          'px" />'
        ].join("")).show();
      }
    };
  },

  video: new function()
  {
    this.test = function(mime,mimes){return!mime.indexOf('video')&&mimes[mime];};
    this.show = function(ql, f)
    {
      if (f.hash == ql._hash)
      {
        ql.ico.hide();
        ql.media.append(['<embed src="', f.url, '" style="width:100%;height:', ql.mediaHeight(), 'px" />'].join("")).show();
      }
    };
  },

  pdf: new function()
  {
    this.test = function(mime,mimes){return(mime=='application/pdf')&&mimes[mime];};
    this.show = function(ql, f)
    {
      if (f.hash == ql._hash)
      {
        ql.ico.hide();
        ql.media.append(['<embed src="', f.url, '" style="width:100%;height:', ql.mediaHeight(), 'px" />'].join("")).show();
      }
    };
  }
}

})(jQuery);/**
 * @class  Bind/update events
 * @author dio dio@std42.ru
 **/
(function($) {
elFinder.prototype.eventsManager = function(fm, el) {
  var self   = this;
  this.lock  = false;
  this.fm    = fm;
  this.ui    = fm.ui;
  this.tree  = fm.view.tree
  this.cwd   = fm.view.cwd;
  this.pointer = '';
  
  /**
   * Initial events binding
   *
   **/
  this.init = function() {
    
    this.cwd
      .bind('click', function(e) {
        var t = $(e.target);
        if (t.hasClass('ui-selected')) {
          self.fm.unselectAll();
        } else {
          if (!t.attr('key')) {
            t = t.parent('[key]');
          }
          if (e.ctrlKey || e.metaKey) {
            self.fm.toggleSelect(t);
          } else {
            self.fm.select(t, true);
          }
        }
      })
      .bind(window.opera?'click':'contextmenu', function(e) {
        if (window.opera && !e.ctrlKey) {
          return;
        }
        var t = $(e.target);
        e.preventDefault();
        e.stopPropagation()
        if (t.hasClass('el-finder-cwd')) {
          self.fm.unselectAll();
        } else {
          self.fm.select(t.attr('key') ? t : t.parent('[key]'));
        }
        self.fm.quickLook.hide();
        self.fm.ui.showMenu(e);
      })
      .selectable({
        filter : '[key]',
        delay  : 300,
        stop   : function() {self.fm.updateSelect() }
      });

  // НА
    $.addDocHandler("click", function()
    {
      self.fm.ui.hideMenu(); 
      $('input', self.cwd).trigger('change'); 
    });

    /* open parents dir in tree */
    this.tree.bind('select', function(e) {
      self.tree.find('a').removeClass('selected');
      $(e.target).addClass('selected').parents('li:has(ul)').children('ul').show().prev().children('div').addClass('expanded');
    });
    
    /* make places droppable */
    if (this.fm.options.places) {

      this.fm.view.plc.click(function(e) {
        e.preventDefault();
        var t = $(e.target),
          h = t.attr('key'), ul;
        
        if (h) {
          h != self.fm.cwd.hash && self.ui.exec('open', e.target)
        } else if (e.target.nodeName == 'A' || e.target.nodeName == 'DIV') {
          ul = self.fm.view.plc.find('ul');
          if (ul.children().length) {
            ul.toggle(300);
            self.fm.view.plc.children('li').find('div').toggleClass('expanded');
          }
        }
      });
      
      this.fm.view.plc.droppable({
        accept    : '(div,tr).directory',
        tolerance : 'pointer',
        over      : function() {$(this).addClass('el-finder-droppable'); },
        out       : function() {$(this).removeClass('el-finder-droppable'); },
        drop      : function(e, ui) {
          $(this).removeClass('el-finder-droppable');
          var upd = false;
          /* accept only folders with read access */
          ui.helper.children('.directory:not(.noaccess,.dropbox)').each(function() {
            if (self.fm.addPlace($(this).attr('key'))) {
              upd = true;
              $(this).hide();
            }
          });
          /* update places id's and view */
          if (upd) {
            self.fm.view.renderPlaces();
            self.updatePlaces();
            self.fm.view.plc.children('li').children('div').trigger('click');
          }
          /* hide helper if empty */
          if (!ui.helper.children('div:visible').length) {
            ui.helper.hide();
          }
        }
      });
    }

    /* bind shortcuts */
  // НА
    $.addDocHandler($.browser.mozilla || $.browser.opera ? "keypress" : "keydown", function(e)
    {
      var meta = e.ctrlKey||e.metaKey;

      if (self.lock) {
        return;
      }
      switch(e.keyCode) {
        /* arrows left/up. with Ctrl - exec "back", w/o - move selection */
        case 37:
        case 38:
          e.stopPropagation();
          e.preventDefault();
          if (e.keyCode == 37 && meta) {
            self.ui.execIfAllowed('back');
          } else {
            moveSelection(false, !e.shiftKey);
          }
          break;
        /* arrows right/down. with Ctrl - exec "open", w/o - move selection  */
        case 39:
        case 40:
          e.stopPropagation();
          e.preventDefault();
          if (meta) {
            self.ui.execIfAllowed('open');
          } else {
            moveSelection(true, !e.shiftKey);
          }
          break;
      }
    });

  // НА
    $.addDocHandler($.browser.opera ? "keypress" : "keydown", function(e)
    {
      if (self.lock) {
        return;
      }
      switch(e.keyCode) {
        /* Space - QuickLook */
        case 32:
          e.preventDefault();
          e.stopPropagation();
          self.fm.quickLook.toggle();
          break;
        /* Esc */  
        case 27:
          self.fm.quickLook.hide();
          break;
      }
    });

    if (!this.fm.options.disableShortcuts) {
      
      $(document).bind('keydown', function(e) {
        var meta = e.ctrlKey||e.metaKey;

        if (self.lock) {
          return;
        }
        switch (e.keyCode) {
          /* Meta+Backspace - delete */
          case 8:
            if (meta && self.ui.isCmdAllowed('rm')) {
              e.preventDefault();
              self.ui.exec('rm');
            } 
            break;
          /* Enter - exec "select" command if enabled, otherwise exec "open" */  
          case 13:
            if (self.ui.isCmdAllowed('select')) {
              return self.ui.exec('select');
            }
            self.ui.execIfAllowed('open');
            break;
          /* Delete */
          case 46:
            self.ui.execIfAllowed('rm');
            break;
          /* Ctrl+A */
          case 65:
            if (meta) {
              e.preventDefault();
              self.fm.selectAll();
            }
            break;
          /* Ctrl+C */
          case 67:
            meta && self.ui.execIfAllowed('copy');
            break;
          /* Ctrl+I - get info */  
          case 73:
            if (meta) {
              e.preventDefault();
              self.ui.exec('info');
            }
            break;
          /* Ctrl+N - new folder */
          case 78:
            if (meta) {
              e.preventDefault();
              self.ui.execIfAllowed('mkdir');
            }
            break;
          /* Ctrl+U - upload files */
          case 85:
          
            if (meta) {
              e.preventDefault();
              self.ui.execIfAllowed('upload');
            }
            break;
          /* Ctrl+V */
          case 86:
            meta && self.ui.execIfAllowed('paste');
            break;
          /* Ctrl+X */
          case 88:
            meta && self.ui.execIfAllowed('cut');
            break;
            
          case 113:
            self.ui.execIfAllowed('rename');
            break;
            
        }

      });
      
    }
    
  }
  
  /**
   * Update navigation droppable/draggable
   *
   **/
  this.updateNav = function() {
    $('a', this.tree).click(function(e) {
      e.preventDefault();
      var t = $(this), c;
      if (e.target.nodeName == 'DIV' && $(e.target).hasClass('collapsed')) {
        $(e.target).toggleClass('expanded').parent().next('ul').toggle(300);
      } else if (t.attr('key') != self.fm.cwd.hash) {
        if (t.hasClass('noaccess') || t.hasClass('dropbox')) {
          self.fm.view.error('Access denied');
        } else {
          self.ui.exec('open', t.trigger('select')[0]);
        }
      } else {
        c = t.children('.collapsed');
        if (c.length) {
          c.toggleClass('expanded');
          t.next('ul').toggle(300);
        }
      }
    });
    
    $('a:not(.noaccess,.readonly)', this.tree).droppable({
      tolerance : 'pointer',
      accept : '(div,tr)[key]',
      over   : function() {$(this).addClass('el-finder-droppable'); },
      out    : function() {$(this).removeClass('el-finder-droppable'); },
      drop   : function(e, ui) {$(this).removeClass('el-finder-droppable'); self.fm.drop(e, ui, $(this).attr('key')); }
    });
    this.fm.options.places && this.updatePlaces();
  }
  
  /**
   * Update places draggable
   *
   **/
  this.updatePlaces = function() {
    this.fm.view.plc.children('li').find('li').draggable({
      scroll : false,
      stop   : function() {
        if (self.fm.removePlace($(this).children('a').attr('key'))) {
          $(this).remove();
          if (!$('li', self.fm.view.plc.children('li')).length) {
            self.fm.view.plc.children('li').find('div').removeClass('collapsed expanded').end().children('ul').hide();
          }
        }
      }
    });
  }
  
  /**
   * Update folders droppable & files/folders draggable
   **/
  this.updateCwd = function() {
    
    $('[key]', this.cwd)
      .bind('dblclick', function(e) {
        self.fm.select($(this), true);
        self.ui.exec(self.ui.isCmdAllowed('select') ? 'select' : 'open');
      })
      .draggable({
        delay      : 3,
        addClasses : false,
        appendTo : '.el-finder-cwd',
        revert     : true,
        drag       : function(e, ui) {
          ui.helper.toggleClass('el-finder-drag-copy', e.shiftKey||e.ctrlKey);
        },
        helper     : function() {
          var t = $(this),
            h = $('<div class="el-finder-drag-helper"/>'),
            c = 0;
          !t.hasClass('ui-selected') && self.fm.select(t, true);

          self.cwd.find('.ui-selected').each(function(i) {
            var el = self.fm.options.view == 'icons' ? $(this).clone().removeClass('ui-selected') : $(self.fm.view.renderIcon(self.fm.cdc[$(this).attr('key')]))
            if (c++ == 0 || c%12 == 0) {
              el.css('margin-left', 0);
            }
            h.append(el);
          });
          return h.css('width', (c<=12 ? 85+(c-1)*29 : 387)+'px');
        }
      })
      .filter('.directory')
      .droppable({
        tolerance : 'pointer',
        accept    : '(div,tr)[key]',
        over      : function() {$(this).addClass('el-finder-droppable');  },
        out       : function() {$(this).removeClass('el-finder-droppable'); },
        drop      : function(e, ui) {$(this).removeClass('el-finder-droppable'); self.fm.drop(e, ui, $(this).attr('key')); }
      });
      
    if ($.browser.msie) {
      $('*', this.cwd).attr('unselectable', 'on')
        .filter('[key]')
          .bind('dragstart', function() {self.cwd.selectable('disable').removeClass('ui-state-disabled ui-selectable-disabled'); })
          .bind('dragstop', function() {self.cwd.selectable('enable'); });
    }

  }
  
  /**
   * Move selection in current dir 
   *
   * @param Boolean  move forward?
   * @param Boolean  clear current selection?
   **/
  function moveSelection(forward, reset) {
    var p, _p, cur;
    
    if (!$('[key]', self.cwd).length) {
      return;
    }
    
    if (self.fm.selected.length == 0) {
      p = $('[key]:'+(forward ? 'first' : 'last'), self.cwd);
      self.fm.select(p);
    } else if (reset) {
      p  = $('.ui-selected:'+(forward ? 'last' : 'first'), self.cwd);
      _p = p[forward ? 'next' : 'prev']('[key]');
      if (_p.length) {
        p = _p;
      }
      self.fm.select(p, true);
    } else {
      if (self.pointer) {
        cur = $('[key="'+self.pointer+'"].ui-selected', self.cwd);
      }
      if (!cur || !cur.length) {
        cur = $('.ui-selected:'+(forward ? 'last' : 'first'), self.cwd);
      }
      p = cur[forward ? 'next' : 'prev']('[key]');

      if (!p.length) {
        p = cur;
      } else {
        if (!p.hasClass('ui-selected')) {
          self.fm.select(p);
        } else {
          if (!cur.hasClass('ui-selected')) {
            self.fm.unselect(p);
          } else {
            _p = cur[forward ? 'prev' : 'next']('[key]')
            if (!_p.length || !_p.hasClass('ui-selected')) {
              self.fm.unselect(cur);
            } else {
              while ((_p = forward ? p.next('[key]') : p.prev('[key]')) && p.hasClass('ui-selected')) {
                p = _p;
              }
              self.fm.select(p);
            }
          }
        }
      } 
    }
    self.pointer = p.attr('key');
    self.fm.checkSelectedPos(forward);
  }

}

})(jQuery);
